/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.eazegraph.lib.gesture;

import ohos.agp.components.VelocityDetector;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.multimodalinput.event.TouchEvent;
import org.eazegraph.lib.utils.LogUtil;

/**
 * Gesture detector
 */
public class GestureDetector {

    /**
     * * The constant FLAG_IS_GENERATED_GESTURE
     */
    public static final int FLAG_IS_GENERATED_GESTURE = 0x8;
    /**
     * * The constant TAG
     */
    private static final String TAG = GestureDetector.class.getSimpleName();
    /**
     * * The constant LONGPRESS_TIMEOUT
     */
    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
    /**
     * * The constant TAP_TIMEOUT
     */
    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
    /**
     * * The constant DOUBLE_TAP_TIMEOUT
     */
    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
    /**
     * * The constant DOUBLE_TAP_MIN_TIME
     */
    private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
    /**
     * * The constant SHOW_PRESS
     */
    private static final int SHOW_PRESS = 1;
    /**
     * * The constant LONG_PRESS
     */
    private static final int LONG_PRESS = 2;
    /**
     * * The constant TAP
     */
    private static final int TAP = 3;
    /**
     * The constant M handler
     */
    private final GestureHandler mHandler;
    /**
     * The constant M listener
     */
    private final OnGestureListener mListener;
    /**
     * The constant M touch slop square
     */
    private int mTouchSlopSquare;
    /**
     * The constant M double tap touch slop square
     */
    private int mDoubleTapTouchSlopSquare;
    /**
     * The constant M double tap slop square
     */
    private int mDoubleTapSlopSquare;
    /**
     * The constant M ambiguous gesture multiplier
     */
    private float mAmbiguousGestureMultiplier;
    /**
     * The constant M minimum fling velocity
     */
    private int mMinimumFlingVelocity;
    /**
     * The constant M maximum fling velocity
     */
    private int mMaximumFlingVelocity;
    /**
     * The constant M double tap listener
     */
    private OnDoubleTapListener mDoubleTapListener;
    /**
     * The constant M context click listener
     */
    private OnContextClickListener mContextClickListener;
    /**
     * The constant M still down
     */
    private boolean mStillDown;
    /**
     * The constant M defer confirm single tap
     */
    private boolean mDeferConfirmSingleTap;
    /**
     * The constant M in long press
     */
    private boolean mInLongPress;
    /**
     * The constant M in context click
     */
    private boolean mInContextClick;
    /**
     * The constant M always in tap region
     */
    private boolean mAlwaysInTapRegion;
    /**
     * The constant M always in bigger tap region
     */
    private boolean mAlwaysInBiggerTapRegion;
    /**
     * The constant M ignore next up event
     */
    private boolean mIgnoreNextUpEvent;
    /**
     * The constant M has recorded classification
     */
    private boolean mHasRecordedClassification;

    /**
     * The constant M current down event
     */
    private TouchEvent mCurrentDownEvent;

    /**
     * The constant M current touch event
     */
    private TouchEvent mCurrentTouchEvent;

    /**
     * The constant M previous up event
     */
    private TouchEvent mPreviousUpEvent;

    /**
     * The constant M is double tapping
     */
    private boolean mIsDoubleTapping;
    /**
     * The constant M last focus x
     */
    private float mLastFocusX;
    /**
     * The constant M last focus y
     */
    private float mLastFocusY;
    /**
     * The constant M down focus x
     */
    private float mDownFocusX;
    /**
     * The constant M down focus y
     */
    private float mDownFocusY;
    /**
     * The constant M is longpress enabled
     */
    private boolean mIsLongpressEnabled;
    /**
     * The constant M velocity tracker
     */
    private VelocityDetector mVelocityTracker;

    /**
     * Gesture detector
     *
     * @param listener listener
     * @param handler  handler
     */
    @Deprecated
    public GestureDetector(OnGestureListener listener, EventHandler handler) {
        this(null, listener, handler);
    }

    /**
     * Gesture detector
     *
     * @param listener listener
     */
    @Deprecated
    public GestureDetector(OnGestureListener listener) {
        this(null, listener, null);
    }

    /**
     * Gesture detector
     *
     * @param context  context
     * @param listener listener
     */
    public GestureDetector(Context context, OnGestureListener listener) {
        this(context, listener, null);
    }


    /**
     * Gesture detector
     *
     * @param context  context
     * @param listener listener
     * @param handler  handler
     */
    public GestureDetector(Context context, OnGestureListener listener, EventHandler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler(EventRunner.getMainEventRunner());
        }
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }

    /**
     * Gesture detector
     *
     * @param context  context
     * @param listener listener
     * @param handler  handler
     * @param unused   unused
     */
    public GestureDetector(Context context, OnGestureListener listener, EventHandler handler,
                boolean unused) {
        this(context, listener, handler);
    }

    /**
     * Init *
     *
     * @param context context
     */
    private void init(Context context) {
        if (mListener == null) {
            throw new NullPointerException("OnGestureListener must not be null");
        }
        mIsLongpressEnabled = true;

        // Fallback to support pre-donuts releases
        int touchSlop;
        int doubleTapSlop;
        int doubleTapTouchSlop;
        if (context == null) {
            // noinspection deprecation
            touchSlop = ViewConfiguration.getTouchSlop();
            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
            doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
            // noinspection deprecation
            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
            mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier();
        } else {
            touchSlop = ViewConfiguration.getScaledTouchSlop();
            doubleTapTouchSlop = ViewConfiguration.getScaledDoubleTapTouchSlop();
            doubleTapSlop = ViewConfiguration.getScaledDoubleTapSlop();
            mMinimumFlingVelocity = ViewConfiguration.getScaledMinimumFlingVelocity();
            mMaximumFlingVelocity = ViewConfiguration.getScaledMaximumFlingVelocity();
            mAmbiguousGestureMultiplier = ViewConfiguration.getScaledAmbiguousGestureMultiplier();
        }
        mTouchSlopSquare = touchSlop * touchSlop;
        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
    }

    /**
     * Set on double tap listener *
     *
     * @param onDoubleTapListener on double tap listener
     */
    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
        mDoubleTapListener = onDoubleTapListener;
    }

    /**
     * Set context click listener *
     *
     * @param onContextClickListener on context click listener
     */
    public void setContextClickListener(OnContextClickListener onContextClickListener) {
        mContextClickListener = onContextClickListener;
    }

    /**
     * Set is longpress enabled *
     *
     * @param isLongpressEnabled is longpress enabled
     */
    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
        mIsLongpressEnabled = isLongpressEnabled;
    }

    /**
     * Is longpress enabled boolean
     *
     * @return the boolean
     */
    public boolean isLongpressEnabled() {
        return mIsLongpressEnabled;
    }

    /**
     * On touch event boolean
     *
     * @param ev ev
     * @return the boolean
     */
    public boolean onTouchEvent(TouchEvent ev) {
        final int action = ev.getAction();

        if (mCurrentTouchEvent != null) {
            mCurrentTouchEvent = null;
        }
        mCurrentTouchEvent = ev;

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityDetector.obtainInstance();
        }
        mVelocityTracker.addEvent(ev);

        final boolean pointerUp = action == TouchEvent.PRIMARY_POINT_UP;
        final int skipIndex = pointerUp ? ev.getIndex() : -1;
        final boolean isGeneratedGesture = true;

        // Determine focal point
        float sumX = 0;
        float sumY = 0;
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) {
                continue;
            }
            sumX += ev.getPointerPosition(i).getX();
            sumY += ev.getPointerPosition(i).getY();
        }
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;
        final float focusY = sumY / div;

        boolean handled = false;

        switch (action) {
            case TouchEvent.OTHER_POINT_DOWN:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                // Cancel long press and taps
                cancelTaps();
                break;

            case TouchEvent.OTHER_POINT_UP:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;

                // Check the dot product of current velocities.
                // If the pointer that left was opposing another velocity vector, clear.
                mVelocityTracker.calculateCurrentVelocity(1000, mMaximumFlingVelocity, mMaximumFlingVelocity);
                final int upIndex = ev.getIndex();
                final int id1 = ev.getPointerId(upIndex);
                final float x1 = mVelocityTracker.getHorizontalVelocity();
                final float y1 = mVelocityTracker.getVerticalVelocity();
                for (int i = 0; i < count; i++) {
                    if (i == upIndex) {
                        continue;
                    }

                    final int id2 = ev.getPointerId(i);
                    final float dx = x1 * mVelocityTracker.getHorizontalVelocity();
                    final float dy = y1 * mVelocityTracker.getVerticalVelocity();

                    final float dot = dx + dy;
                    if (dot < 0) {
                        mVelocityTracker.clear();
                        break;
                    }
                }
                break;

            case TouchEvent.PRIMARY_POINT_DOWN:
                if (mDoubleTapListener != null) {
                    boolean hadTapMessage = mHandler.hasInnerEvent(TAP);
                    if (hadTapMessage) {
                        mHandler.removeEvent(TAP);
                    }
                    if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
                            && hadTapMessage
                            && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                        // This is a second tap
                        mIsDoubleTapping = true;

                        // Give a callback with the first tap of the double-tap
                        handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                        // Give a callback with down event of the double-tap
                        handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                    } else {
                        // This is a first tap
                        mHandler.sendEvent(TAP, DOUBLE_TAP_TIMEOUT);
                    }
                }

                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                if (mCurrentDownEvent != null) {
                    mCurrentDownEvent = null;
                }
                mCurrentDownEvent = ev;
                mAlwaysInTapRegion = true;
                mAlwaysInBiggerTapRegion = true;
                mStillDown = true;
                mInLongPress = false;
                mDeferConfirmSingleTap = false;
                mHasRecordedClassification = false;

                if (mIsLongpressEnabled) {
                    mHandler.removeEvent(LONG_PRESS);
                    mHandler.sendTimingEvent(LONG_PRESS, mCurrentDownEvent.getStartTime() + ViewConfiguration.getLongPressTimeout());
                }
                mHandler.sendTimingEvent(SHOW_PRESS,
                        mCurrentDownEvent.getStartTime() + TAP_TIMEOUT);
                handled |= mListener.onDown(ev);
                break;

            case TouchEvent.POINT_MOVE:
                if (mInLongPress || mInContextClick) {
                    break;
                }

                final boolean hasPendingLongPress = mHandler.hasInnerEvent(LONG_PRESS);

                final float scrollX = mLastFocusX - focusX;
                final float scrollY = mLastFocusY - focusY;
                if (mIsDoubleTapping) {
                    // Give the move events of the double-tap

                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                } else if (mAlwaysInTapRegion) {
                    final int deltaX = (int) (focusX - mDownFocusX);
                    final int deltaY = (int) (focusY - mDownFocusY);
                    int distance = (deltaX * deltaX) + (deltaY * deltaY);
                    int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;

                    final boolean shouldInhibitDefaultAction =
                            hasPendingLongPress;
                    if (shouldInhibitDefaultAction) {
                        // Inhibit default long press
                        if (distance > slopSquare) {
                            // The default action here is to remove long press. But if the touch
                            // slop below gets increased, and we never exceed the modified touch
                            // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
                            // will happen in response to user input. To prevent this,
                            // reschedule long press with a modified timeout.
                            mHandler.removeEvent(LONG_PRESS);
                            final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
                            mHandler.sendTimingEvent(LONG_PRESS, ev.getStartTime()
                                    + (long) (longPressTimeout * mAmbiguousGestureMultiplier));
                        }
                        // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
                        // until the gesture is resolved.
                        // However, for safety, simply increase the touch slop in case the
                        // classification is erroneous. Since the value is squared, multiply twice.
                        slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier;
                    }

                    if (distance > slopSquare) {

                        handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                        mLastFocusX = focusX;
                        mLastFocusY = focusY;
                        mAlwaysInTapRegion = false;
                        mHandler.removeEvent(TAP);
                        mHandler.removeEvent(SHOW_PRESS);
                        mHandler.removeEvent(LONG_PRESS);
                    }
                    int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
                    if (distance > doubleTapSlopSquare) {
                        mAlwaysInBiggerTapRegion = false;
                    }
                } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {

                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                }
                break;

            case TouchEvent.PRIMARY_POINT_UP:
                mStillDown = false;
                TouchEvent currentUpEvent = ev;
                if (mIsDoubleTapping) {
                    // Finally, give the up event of the double-tap

                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                } else if (mInLongPress) {
                    mHandler.removeEvent(TAP);
                    mInLongPress = false;
                } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {

                    handled = mListener.onSingleTapUp(ev);
                    if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                        mDoubleTapListener.onSingleTapConfirmed(ev);
                    }
                } else if (!mIgnoreNextUpEvent) {

                    // A fling must travel the minimum tap distance
                    final VelocityDetector velocityTracker = mVelocityTracker;
                    final int pointerId = ev.getPointerId(0);
                    velocityTracker.calculateCurrentVelocity(1000, mMaximumFlingVelocity, mMaximumFlingVelocity);
                    final float velocityY = velocityTracker.getVerticalVelocity();
                    final float velocityX = velocityTracker.getHorizontalVelocity();

                    if ((Math.abs(velocityY) > mMinimumFlingVelocity)
                            || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
                        handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
                    }
                }
                if (mPreviousUpEvent != null) {
                    mPreviousUpEvent = null;
                }
                // Hold the event we obtained above - listeners may have changed the original.
                mPreviousUpEvent = currentUpEvent;
                if (mVelocityTracker != null) {
                    // This may have been cleared when we called out to the
                    // application above.
                    mVelocityTracker.clear();
                    mVelocityTracker = null;
                }
                mIsDoubleTapping = false;
                mDeferConfirmSingleTap = false;
                mIgnoreNextUpEvent = false;
                mHandler.removeEvent(SHOW_PRESS);
                mHandler.removeEvent(LONG_PRESS);
                break;

            case TouchEvent.CANCEL:
                cancel();
                break;
        }


        return handled;
    }

    /**
     * Cancel
     */
    private void cancel() {
        mHandler.removeEvent(SHOW_PRESS);
        mHandler.removeEvent(LONG_PRESS);
        mHandler.removeEvent(TAP);
        mVelocityTracker.clear();
        mVelocityTracker = null;
        mIsDoubleTapping = false;
        mStillDown = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        mInLongPress = false;
        mInContextClick = false;
        mIgnoreNextUpEvent = false;
    }

    /**
     * Cancel taps
     */
    private void cancelTaps() {
        mHandler.removeEvent(SHOW_PRESS);
        mHandler.removeEvent(LONG_PRESS);
        mHandler.removeEvent(TAP);
        mIsDoubleTapping = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        mInLongPress = false;
        mInContextClick = false;
        mIgnoreNextUpEvent = false;
    }

    /**
     * Is considered double tap boolean
     *
     * @param firstDown  first down
     * @param firstUp    first up
     * @param secondDown second down
     * @return the boolean
     */
    private boolean isConsideredDoubleTap(TouchEvent firstDown, TouchEvent firstUp,
                TouchEvent secondDown) {
        if (!mAlwaysInBiggerTapRegion) {
            return false;
        }

        final long deltaTime = secondDown.getOccurredTime() - firstUp.getOccurredTime();
        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
            return false;
        }

        int deltaX = (int) firstDown.getPointerPosition(0).getX() - (int) secondDown.getPointerPosition(0).getX();
        int deltaY = (int) firstDown.getPointerPosition(0).getY() - (int) secondDown.getPointerPosition(0).getY();

        int slopSquare = true ? 0 : mDoubleTapSlopSquare;
        return (deltaX * deltaX + deltaY * deltaY < slopSquare);
    }

    /**
     * Dispatch long press
     */
    private void dispatchLongPress() {
        mHandler.removeEvent(TAP);
        mDeferConfirmSingleTap = false;
        mInLongPress = true;
        mListener.onLongPress(mCurrentDownEvent);
    }

    /**
     * On gesture listener
     */
    public interface OnGestureListener {

        /**
         * On down boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onDown(TouchEvent event);

        /**
         * On show press *
         *
         * @param event event
         */
        void onShowPress(TouchEvent event);

        /**
         * On single tap up boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onSingleTapUp(TouchEvent event);

        /**
         * On scroll boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param distanceX distance x
         * @param distanceY distance y
         * @return the boolean
         */
        boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY);


        /**
         * On long press *
         *
         * @param event event
         */
        void onLongPress(TouchEvent event);

        /**
         * On fling boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param velocityX velocity x
         * @param velocityY velocity y
         * @return the boolean
         */
        boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, float velocityY);
    }


    /**
     * On double tap listener
     */
    public interface OnDoubleTapListener {
        /**
         * On single tap confirmed boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onSingleTapConfirmed(TouchEvent event);

        /**
         * On double tap boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onDoubleTap(TouchEvent event);

        /**
         * On double tap event boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onDoubleTapEvent(TouchEvent event);
    }

    /**
     * On context click listener
     */
    public interface OnContextClickListener {
        /**
         * On context click boolean
         *
         * @param event event
         * @return the boolean
         */
        boolean onContextClick(TouchEvent event);
    }

    /**
     * Simple on gesture listener
     */
    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
            OnContextClickListener {

        /**
         * On single tap up boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onSingleTapUp(TouchEvent event) {
            return false;
        }

        /**
         * On long press *
         *
         * @param event event
         */
        public void onLongPress(TouchEvent event) {
        }

        /**
         * On scroll boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param distanceX distance x
         * @param distanceY distance y
         * @return the boolean
         */
        public boolean onScroll(TouchEvent e1, TouchEvent e2,
                                float distanceX, float distanceY) {
            return false;
        }

        /**
         * On fling boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param velocityX velocity x
         * @param velocityY velocity y
         * @return the boolean
         */
        public boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX,
                float velocityY) {
            return false;
        }

        /**
         * On show press *
         *
         * @param event event
         */
        public void onShowPress(TouchEvent event) {
        }

        /**
         * On down boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onDown(TouchEvent event) {
            return false;
        }

        /**
         * On double tap boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onDoubleTap(TouchEvent event) {
            return false;
        }

        /**
         * On double tap event boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onDoubleTapEvent(TouchEvent event) {
            return false;
        }

        /**
         * On single tap confirmed boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onSingleTapConfirmed(TouchEvent event) {
            return false;
        }

        /**
         * On context click boolean
         *
         * @param event event
         * @return the boolean
         */
        public boolean onContextClick(TouchEvent event) {
            return false;
        }
    }

    /**
     * View configuration
     */
    public static class ViewConfiguration {
        /**
         * * The constant DEFAULT_LONG_PRESS_TIMEOUT
         */
        public static final int DEFAULT_LONG_PRESS_TIMEOUT = 400;
        /**
         * * The constant TAG
         */
        private static final String TAG = "ViewConfiguration";
        /**
         * * The constant TAP_TIMEOUT
         */
        private static final int TAP_TIMEOUT = 100;
        /**
         * * The constant DOUBLE_TAP_TIMEOUT
         */
        private static final int DOUBLE_TAP_TIMEOUT = 300;
        /**
         * * The constant DOUBLE_TAP_MIN_TIME
         */
        private static final int DOUBLE_TAP_MIN_TIME = 40;
        /**
         * * The constant TOUCH_SLOP
         */
        private static final int TOUCH_SLOP = 8;
        /**
         * * The constant DOUBLE_TAP_SLOP
         */
        private static final int DOUBLE_TAP_SLOP = 100;
        /**
         * * The constant MINIMUM_FLING_VELOCITY
         */
        private static final int MINIMUM_FLING_VELOCITY = 50;
        /**
         * * The constant MAXIMUM_FLING_VELOCITY
         */
        private static final int MAXIMUM_FLING_VELOCITY = 8000;
        /**
         * * The constant AMBIGUOUS_GESTURE_MULTIPLIER
         */
        private static final float AMBIGUOUS_GESTURE_MULTIPLIER = 2f;
        /**
         * * The constant mMinimumFlingVelocity
         */
        private static int mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
        /**
         * * The constant mMaximumFlingVelocity
         */
        private static int mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
        /**
         * * The constant mTouchSlop
         */
        private static int mTouchSlop = TOUCH_SLOP;
        /**
         * * The constant DOUBLE_TAP_TOUCH_SLOP
         */
        private static int DOUBLE_TAP_TOUCH_SLOP = TOUCH_SLOP;
        /**
         * * The constant mDoubleTapTouchSlop
         */
        private static int mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
        /**
         * * The constant mDoubleTapSlop
         */
        private static int mDoubleTapSlop = DOUBLE_TAP_SLOP;
        /**
         * * The constant mAmbiguousGestureMultiplier
         */
        private static float mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER;

        /**
         * Get long press timeout int
         *
         * @return the int
         */
        public static int getLongPressTimeout() {
            return DEFAULT_LONG_PRESS_TIMEOUT;
        }


        /**
         * Get tap timeout int
         *
         * @return the int
         */
        public static int getTapTimeout() {
            return TAP_TIMEOUT;
        }

        /**
         * Get double tap timeout int
         *
         * @return the int
         */
        public static int getDoubleTapTimeout() {
            return DOUBLE_TAP_TIMEOUT;
        }

        /**
         * Get double tap min time int
         *
         * @return the int
         */
        public static int getDoubleTapMinTime() {
            return DOUBLE_TAP_MIN_TIME;
        }

        /**
         * Get touch slop int
         *
         * @return the int
         */
        public static int getTouchSlop() {
            return TOUCH_SLOP;
        }

        /**
         * Get scaled touch slop int
         *
         * @return the int
         */
        public static int getScaledTouchSlop() {
            return mTouchSlop;
        }

        /**
         * Get scaled double tap touch slop int
         *
         * @return the int
         */
        public static int getScaledDoubleTapTouchSlop() {
            return mDoubleTapTouchSlop;
        }


        /**
         * Get double tap slop int
         *
         * @return the int
         */
        public static int getDoubleTapSlop() {
            return DOUBLE_TAP_SLOP;
        }

        /**
         * Get scaled double tap slop int
         *
         * @return the int
         */
        public static int getScaledDoubleTapSlop() {
            return mDoubleTapSlop;
        }

        /**
         * Get minimum fling velocity int
         *
         * @return the int
         */
        public static int getMinimumFlingVelocity() {
            return MINIMUM_FLING_VELOCITY;
        }

        /**
         * Get scaled minimum fling velocity int
         *
         * @return the int
         */
        public static int getScaledMinimumFlingVelocity() {
            return mMinimumFlingVelocity;
        }

        /**
         * Get maximum fling velocity int
         *
         * @return the int
         */
        public static int getMaximumFlingVelocity() {
            return MAXIMUM_FLING_VELOCITY;
        }

        /**
         * Get scaled maximum fling velocity int
         *
         * @return the int
         */
        public static int getScaledMaximumFlingVelocity() {
            return mMaximumFlingVelocity;
        }


        /**
         * Get ambiguous gesture multiplier float
         *
         * @return the float
         */
        public static float getAmbiguousGestureMultiplier() {
            return AMBIGUOUS_GESTURE_MULTIPLIER;
        }


        /**
         * Get scaled ambiguous gesture multiplier float
         *
         * @return the float
         */
        public static float getScaledAmbiguousGestureMultiplier() {
            return mAmbiguousGestureMultiplier;
        }


    }

    /**
     * Gesture handler
     */
    private class GestureHandler extends EventHandler {

        /**
         * Gesture handler
         *
         * @param runner runner
         * @throws IllegalArgumentException illegal argument exception
         */
        public GestureHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
        }


        /**
         * Gesture handler
         *
         * @param handler handler
         */
        public GestureHandler(EventHandler handler) {
            super(handler.getEventRunner());
        }

        /**
         * Process event *
         *
         * @param event event
         */
        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            LogUtil.info(TAG, "processEvent: " + event.eventId);
            switch (event.eventId) {
                case SHOW_PRESS:
                    mListener.onShowPress(mCurrentDownEvent);
                    break;

                case LONG_PRESS:
                    dispatchLongPress();
                    break;

                case TAP:
                    // If the user's finger is still down, do not count it as a tap
                    if (mDoubleTapListener != null) {
                        if (!mStillDown) {

                            mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                        } else {
                            mDeferConfirmSingleTap = true;
                        }
                    }
                    break;

                default:
            }
        }

    }


}
